<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <title>日志 - 为企业级框架和应用而生</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/zh-cn/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/core/logger.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/core/logger.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>日志</h1>
    <p>日志对于 Web 开发的重要性毋庸置疑，它对于监控应用的运行状态、问题排查等都有非常重要的意义。</p>
<p>框架内置了强大的企业级日志支持，由 <a href="https://github.com/eggjs/egg-logger" target="_blank" rel="noopener">egg-logger</a> 模块提供。</p>
<p>主要特性：</p>
<ul>
<li>日志分级</li>
<li>统一错误日志，所有 logger 中使用 <code>.error()</code> 打印的 <code>ERROR</code> 级别日志都会打印到统一的错误日志文件中，便于追踪</li>
<li>启动日志和运行日志分离</li>
<li>自定义日志</li>
<li>多进程日志</li>
<li>自动切割日志</li>
<li>高性能</li>
</ul>
<h2 id="日志路径"><a class="markdown-anchor" href="#日志路径">#</a> 日志路径</h2>
<ul>
<li>所有日志文件默认都放在 <code>${appInfo.root}/logs/${appInfo.name}</code> 路径下，例如 <code>/home/admin/logs/example-app</code>。</li>
<li>在本地开发环境 (env: local) 和单元测试环境 (env: unittest)，为了避免冲突以及集中管理，日志会打印在项目目录下的 logs 目录，例如 <code>/path/to/example-app/logs/example-app</code>。</li>
</ul>
<p>如果想自定义日志路径：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  dir: <span class="string">'/path/to/your/custom/log/dir'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="日志分类"><a class="markdown-anchor" href="#日志分类">#</a> 日志分类</h2>
<p>框架内置了几种日志，分别在不同的场景下使用：</p>
<ul>
<li>appLogger <code>${appInfo.name}-web.log</code>，例如 <code>example-app-web.log</code>，应用相关日志，供应用开发者使用的日志。我们在绝大数情况下都在使用它。</li>
<li>coreLogger <code>egg-web.log</code> 框架内核、插件日志。</li>
<li>errorLogger <code>common-error.log</code> 实际一般不会直接使用它，任何 logger 的 <code>.error()</code> 调用输出的日志都会重定向到这里，重点通过查看此日志定位异常。</li>
<li>agentLogger <code>egg-agent.log</code> agent 进程日志，框架和使用到 agent 进程执行任务的插件会打印一些日志到这里。</li>
</ul>
<p>如果想自定义以上日志文件名称，可以在 config 文件中覆盖默认值：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">appInfo</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    logger: &#123;</span><br><span class="line">      appLogName: <span class="string">`<span class="subst">$&#123;appInfo.name&#125;</span>-web.log`</span>,</span><br><span class="line">      coreLogName: <span class="string">'egg-web.log'</span>,</span><br><span class="line">      agentLogName: <span class="string">'egg-agent.log'</span>,</span><br><span class="line">      errorLogName: <span class="string">'common-error.log'</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="如何打印日志"><a class="markdown-anchor" href="#如何打印日志">#</a> 如何打印日志</h2>
<h3 id="context-logger"><a class="markdown-anchor" href="#context-logger">#</a> Context Logger</h3>
<p>如果我们在处理请求时需要打印日志，这时候使用 Context Logger，用于记录 Web 行为相关的日志。</p>
<p>每行日志会自动记录上当前请求的一些基本信息，
如 <code>[$userId/$ip/$traceId/${cost}ms $method $url]</code>。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.logger.debug(<span class="string">'debug info'</span>);</span><br><span class="line">ctx.logger.info(<span class="string">'some request data: %j'</span>, ctx.request.body);</span><br><span class="line">ctx.logger.warn(<span class="string">'WARNNING!!!!'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 错误日志记录，直接会将错误日志完整堆栈信息记录下来，并且输出到 errorLog 中</span></span><br><span class="line"><span class="comment">// 为了保证异常可追踪，必须保证所有抛出的异常都是 Error 类型，因为只有 Error 类型才会带上堆栈信息，定位到问题。</span></span><br><span class="line">ctx.logger.error(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'whoops'</span>));</span><br></pre></td></tr></table></figure>
<p>对于框架开发者和插件开发者会使用到的 Context Logger 还有 <code>ctx.coreLogger</code>。</p>
<p>例如</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.coreLogger.info(<span class="string">'info'</span>);</span><br></pre></td></tr></table></figure>
<h3 id="app-logger"><a class="markdown-anchor" href="#app-logger">#</a> App Logger</h3>
<p>如果我们想做一些应用级别的日志记录，如记录启动阶段的一些数据信息，可以通过 App Logger 来完成。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  app.logger.debug(<span class="string">'debug info'</span>);</span><br><span class="line">  app.logger.info(<span class="string">'启动耗时 %d ms'</span>, <span class="built_in">Date</span>.now() - start);</span><br><span class="line">  app.logger.warn(<span class="string">'warning!'</span>);</span><br><span class="line"></span><br><span class="line">  app.logger.error(someErrorObj);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>对于框架和插件开发者会使用到的 App Logger 还有 <code>app.coreLogger</code>。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  app.coreLogger.info(<span class="string">'启动耗时 %d ms'</span>, <span class="built_in">Date</span>.now() - start);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="agent-logger"><a class="markdown-anchor" href="#agent-logger">#</a> Agent Logger</h3>
<p>在开发框架和插件时有时会需要在 Agent 进程运行代码，这时使用 <code>agent.coreLogger</code>。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// agent.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">agent</span> =&gt;</span> &#123;</span><br><span class="line">  agent.logger.debug(<span class="string">'debug info'</span>);</span><br><span class="line">  agent.logger.info(<span class="string">'启动耗时 %d ms'</span>, <span class="built_in">Date</span>.now() - start);</span><br><span class="line">  agent.logger.warn(<span class="string">'warning!'</span>);</span><br><span class="line"></span><br><span class="line">  agent.logger.error(someErrorObj);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>如需详细了解 Agent 进程，请参考<a href="./cluster-and-ipc.html">多进程模型</a>。</p>
<h2 id="日志文件编码"><a class="markdown-anchor" href="#日志文件编码">#</a> 日志文件编码</h2>
<p>默认编码为 <code>utf-8</code>，可通过如下方式覆盖：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  encoding: <span class="string">'gbk'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="日志级别"><a class="markdown-anchor" href="#日志级别">#</a> 日志级别</h2>
<p>日志分为 <code>NONE</code>，<code>DEBUG</code>，<code>INFO</code>，<code>WARN</code> 和 <code>ERROR</code> 5 个级别。</p>
<p>日志打印到文件中的同时，为了方便开发，也会同时打印到终端中。</p>
<h3 id="文件日志级别"><a class="markdown-anchor" href="#文件日志级别">#</a> 文件日志级别</h3>
<p>默认只会输出 <code>INFO</code> 及以上（<code>WARN</code> 和 <code>ERROR</code>）的日志到文件中。</p>
<p>可通过如下方式配置输出到文件日志的级别：</p>
<p>打印所有级别日志到文件中：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  level: <span class="string">'DEBUG'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>关闭所有打印到文件的日志：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  level: <span class="string">'NONE'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="终端日志级别"><a class="markdown-anchor" href="#终端日志级别">#</a> 终端日志级别</h3>
<p>默认只会输出 <code>INFO</code> 及以上（<code>WARN</code> 和 <code>ERROR</code>）的日志到终端中。</p>
<ul>
<li><code>logger.consoleLevel</code>: 输出到终端日志的级别，默认为 <code>INFO</code></li>
</ul>
<p>可通过如下方式配置输出到终端日志的级别：</p>
<p>打印所有级别日志到终端：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  consoleLevel: <span class="string">'DEBUG'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>关闭所有打印到终端的日志：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  consoleLevel: <span class="string">'NONE'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="自定义日志"><a class="markdown-anchor" href="#自定义日志">#</a> 自定义日志</h2>
<h3 id="增加自定义日志"><a class="markdown-anchor" href="#增加自定义日志">#</a> 增加自定义日志</h3>
<p><strong>一般应用无需配置自定义日志</strong>，因为日志打太多或太分散都会导致关注度分散，反而难以管理和难以排查发现问题。</p>
<p>如果实在有需求可以如下配置：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">appInfo</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    customLogger: &#123;</span><br><span class="line">      xxLogger: &#123;</span><br><span class="line">        file: path.join(appInfo.root, <span class="string">'logs/xx.log'</span>),</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>可通过 <code>app.getLogger('xxLogger')</code> / <code>ctx.getLogger('xxLogger')</code> 获取，最终的打印结果和 coreLogger 类似。</p>
<h3 id="高级自定义日志"><a class="markdown-anchor" href="#高级自定义日志">#</a> 高级自定义日志</h3>
<p>日志默认是打印到日志文件中，当本地开发时同时会打印到终端。
但是，有时候我们会有需求把日志打印到其他媒介上，这时候我们就需要自定义日志的 transport。</p>
<p>Transport 是一种传输通道，一个 logger 可包含多个传输通道。比如默认的 logger 就有 fileTransport 和 consoleTransport 两个通道，
分别负责打印到文件和终端。</p>
<p>举个例子，我们不仅需要把错误日志打印到 <code>common-error.log</code>，还需要上报给第三方服务。</p>
<p>首先我们定义一个日志的 transport，代表第三方日志服务。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">'util'</span>);</span><br><span class="line"><span class="keyword">const</span> Transport = <span class="built_in">require</span>(<span class="string">'egg-logger'</span>).Transport;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RemoteErrorTransport</span> <span class="keyword">extends</span> <span class="title">Transport</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 定义 log 方法，在此方法中把日志上报给远端服务</span></span><br><span class="line">  log(level, args) &#123;</span><br><span class="line">    <span class="keyword">let</span> log;</span><br><span class="line">    <span class="keyword">if</span> (args[<span class="number">0</span>] <span class="keyword">instanceof</span> <span class="built_in">Error</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> err = args[<span class="number">0</span>];</span><br><span class="line">      log = util.format(<span class="string">'%s: %s\n%s\npid: %s\n'</span>, err.name, err.message, err.stack, process.pid);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      log = util.format(...args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.options.app.curl(<span class="string">'http://url/to/remote/error/log/service/logs'</span>, &#123;</span><br><span class="line">      data: log,</span><br><span class="line">      method: <span class="string">'POST'</span>,</span><br><span class="line">    &#125;).catch(<span class="built_in">console</span>.error);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// app.js 中给 errorLogger 添加 transport，这样每条日志就会同时打印到这个 transport 了</span></span><br><span class="line">app.getLogger(<span class="string">'errorLogger'</span>).set(<span class="string">'remote'</span>, <span class="keyword">new</span> RemoteErrorTransport(&#123; <span class="attr">level</span>: <span class="string">'ERROR'</span>, app &#125;));</span><br></pre></td></tr></table></figure>
<p>上面的例子比较简单，实际情况中我们需要考虑性能，很可能采取先打印到内存，再定时上传的策略，以提高性能。</p>
<h2 id="日志切割"><a class="markdown-anchor" href="#日志切割">#</a> 日志切割</h2>
<p>企业级日志一个最常见的需求之一是对日志进行自动切割，以方便管理。框架对日志切割的支持由 <a href="https://github.com/eggjs/egg-logrotator" target="_blank" rel="noopener">egg-logrotator</a> 插件提供。</p>
<h3 id="按天切割"><a class="markdown-anchor" href="#按天切割">#</a> 按天切割</h3>
<p>这是框架的默认日志切割方式，在每日 <code>00:00</code> 按照 <code>.log.YYYY-MM-DD</code> 文件名进行切割。</p>
<p>以 appLog 为例，当前写入的日志为 <code>example-app-web.log</code>，当凌晨 <code>00:00</code> 时，会对日志进行切割，把过去一天的日志按 <code>example-app-web.log.YYYY-MM-DD</code> 的形式切割为单独的文件。</p>
<h3 id="按照文件大小切割"><a class="markdown-anchor" href="#按照文件大小切割">#</a> 按照文件大小切割</h3>
<p>我们也可以按照文件大小进行切割。例如，当文件超过 2G 时进行切割。</p>
<p>例如，我们需要把 <code>egg-web.log</code> 按照大小进行切割：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">appInfo</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    logrotator: &#123;</span><br><span class="line">      filesRotateBySize: [</span><br><span class="line">        path.join(appInfo.root, <span class="string">'logs'</span>, appInfo.name, <span class="string">'egg-web.log'</span>),</span><br><span class="line">      ],</span><br><span class="line">      maxFileSize: <span class="number">2</span> * <span class="number">1024</span> * <span class="number">1024</span> * <span class="number">1024</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>添加到 <code>filesRotateBySize</code> 的日志文件不再按天进行切割。</p>
<h3 id="按照小时切割"><a class="markdown-anchor" href="#按照小时切割">#</a> 按照小时切割</h3>
<p>我们也可以选择按照小时进行切割，这和默认的按天切割非常类似，只是时间缩短到每小时。</p>
<p>例如，我们需要把 <code>common-error.log</code> 按照小时进行切割：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">appInfo</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    logrotator: &#123;</span><br><span class="line">      filesRotateByHour: [</span><br><span class="line">        path.join(appInfo.root, <span class="string">'logs'</span>, appInfo.name, <span class="string">'common-error.log'</span>),</span><br><span class="line">      ],</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>添加到 <code>filesRotateByHour</code> 的日志文件不再被按天进行切割。</p>
<h2 id="性能"><a class="markdown-anchor" href="#性能">#</a> 性能</h2>
<p>通常 Web 访问是高频访问，每次打印日志都写磁盘会造成频繁磁盘 IO，为了提高性能，我们采用的文件日志写入策略是：</p>
<blockquote>
<p>日志同步写入内存，异步每隔一段时间(默认 1 秒)刷盘</p>
</blockquote>
<p>更多详细请参考 <a href="https://github.com/eggjs/egg-logger" target="_blank" rel="noopener">egg-logger</a> 和 <a href="https://github.com/eggjs/egg-logrotator" target="_blank" rel="noopener">egg-logrotator</a>。</p>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/core/logger.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/core/logger.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">新手指南<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/zh-cn/intro/index.html" class="menu-link">Egg.js 是什么?</a></li><li><a href="/zh-cn/intro/egg-and-koa.html" class="menu-link">Egg.js 和 Koa</a></li><li><a href="/zh-cn/intro/quickstart.html" class="menu-link">快速入门</a></li><li><a href="/zh-cn/tutorials/progressive.html" class="menu-link">渐进式开发</a></li><li><a href="/zh-cn/migration.html" class="menu-link">2.x 升级指南</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">基础功能<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/zh-cn/basics/structure.html" class="menu-link">目录结构</a></li><li><a href="/zh-cn/basics/objects.html" class="menu-link">内置对象</a></li><li><a href="/zh-cn/basics/env.html" class="menu-link">运行环境</a></li><li><a href="/zh-cn/basics/config.html" class="menu-link">配置</a></li><li><a href="/zh-cn/basics/middleware.html" class="menu-link">中间件</a></li><li><a href="/zh-cn/basics/router.html" class="menu-link">Router</a></li><li><a href="/zh-cn/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/zh-cn/basics/service.html" class="menu-link">Service</a></li><li><a href="/zh-cn/basics/plugin.html" class="menu-link">插件</a></li><li><a href="/zh-cn/basics/schedule.html" class="menu-link">定时任务</a></li><li><a href="/zh-cn/basics/extend.html" class="menu-link">框架扩展</a></li><li><a href="/zh-cn/basics/app-start.html" class="menu-link">启动自定义</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">核心功能<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/zh-cn/core/development.html" class="menu-link">本地开发</a></li><li><a href="/zh-cn/core/unittest.html" class="menu-link">单元测试</a></li><li><a href="/zh-cn/core/deployment.html" class="menu-link">应用部署</a></li><li><a href="/zh-cn/core/logger.html" class="menu-link">日志</a></li><li><a href="/zh-cn/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/zh-cn/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/zh-cn/core/cluster-and-ipc.html" class="menu-link">多进程模型和进程间通讯</a></li><li><a href="/zh-cn/core/view.html" class="menu-link">模板渲染</a></li><li><a href="/zh-cn/core/error-handling.html" class="menu-link">异常处理</a></li><li><a href="/zh-cn/core/security.html" class="menu-link">安全</a></li><li><a href="/zh-cn/core/i18n.html" class="menu-link">国际化</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">教程<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/zh-cn/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/zh-cn/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/zh-cn/tutorials/passport.html" class="menu-link">Passport 鉴权</a></li><li><a href="/zh-cn/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/zh-cn/tutorials/assets.html" class="menu-link">静态资源</a></li><li><a href="/zh-cn/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">进阶<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/zh-cn/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/zh-cn/advanced/plugin.html" class="menu-link">插件开发</a></li><li><a href="/zh-cn/advanced/framework.html" class="menu-link">框架开发</a></li><li><a href="/zh-cn/advanced/cluster-client.html" class="menu-link">多进程研发模式增强</a></li><li><a href="/zh-cn/advanced/view-plugin.html" class="menu-link">模板插件开发规范</a></li><li><a href="/zh-cn/style-guide.html" class="menu-link">代码风格指南</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">社区<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/zh-cn/plugins/" class="menu-link">内置插件列表</a></li><li><a href="/zh-cn/contributing.html" class="menu-link">如何贡献</a></li><li><a href="/zh-cn/resource.html" class="menu-link">资源</a></li><li><a href="/zh-cn/faq.html" class="menu-link">常见问题</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
